package com.nudge.portlet;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.portlet.GenericPortlet;
import javax.portlet.PortletException;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import com.nudge.portlet.api.GranularityType;
import com.nudge.portlet.chart.ConsolidatePoint;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Generic portlet.
 *
 * @author Frederic Massart
 */
public class NudgePortlet extends GenericPortlet {

    private static final Logger LOG = LoggerFactory.getLogger(NudgePortlet.class);

    private MapNudgeData mapNudgeData;


    public void init() throws PortletException {
        // editJSP = getInitParameter("edit-jsp");
        viewJSP = getInitParameter("view-jsp");
    }

    protected String editJSP;

    protected String viewJSP;


    public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, IOException {

        LOG.info("enter doView ");

        // not currently used
        PortletPreferences prefs = renderRequest.getPreferences();


        // get the param from the properties file
        NudgePortletProperties nudgePortletProperties = new NudgePortletProperties();
        Map<String, Object> propertiesMap = nudgePortletProperties.readPropertiesFile();

        // default value ?
        long startTimestamp = 1300451400000l;  //Fri Mar 18 13:30:00 CET 2011
        long endTimestamp = 1400537800000l;  //Tue May 20 00:16:40 CEST 2014


        PeriodAttributes periodAttributes = definePeriod("hour", new DateTime(2011, 7, 8, 15, 0, 0, 0));

        propertiesMap.put("start", periodAttributes.getCurrentStartTimestamp());
        propertiesMap.put("end", periodAttributes.getCurrentEndTimestamp());
        propertiesMap.put("granularity", periodAttributes.getGranularityType());

        String server = null;
        String segment = null;
        ProcessNudgeInteraction processNudgeInteraction = new ProcessNudgeInteraction();
        List<NudgeData> nudgeDataList = processNudgeInteraction.collectApplicationData(propertiesMap, startTimestamp, endTimestamp, server, segment, GranularityType.ONE_HOUR);


        renderRequest.setAttribute("myprop", propertiesMap.get("test"));


        renderRequest.setAttribute("dataList", nudgeDataList);
        // also set to the request the size of the list
        renderRequest.setAttribute("dataListSize", nudgeDataList.size());

        include(viewJSP, renderRequest, renderResponse);


    }

    public void doEdit(RenderRequest renderRequest, RenderResponse renderResponse) {

    }


    private void include(String path, RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, IOException {
        PortletRequestDispatcher portletRequestDispatcher = getPortletContext().getRequestDispatcher(path);
        if (portletRequestDispatcher == null) {
            LOG.error(path + " is not a valid include");
        } else {
            portletRequestDispatcher.include(renderRequest, renderResponse);
        }
    }

    public MapNudgeData getMapNudgeData() {
        if (mapNudgeData == null) {
            mapNudgeData = new MapNudgeData();
        }
        return mapNudgeData;
    }

    public void setMapNudgeData(MapNudgeData mapNudgeData) {
        this.mapNudgeData = mapNudgeData;
    }

    /**
     * Define a time interval from the given date and the duration (hour/day/week).
     *
     * @param duration the selected period
     * @param date     the chosen date to determine the period
     * @return a PeriodAttributes object, null if the duration or the date are incorrect
     */
    public PeriodAttributes definePeriod(final String duration, final DateTime date) {

        // TODO test arguments
        PeriodAttributes periodAttributes = new PeriodAttributes();

        periodAttributes.setCurrentEndTimestamp(date.toDate().getTime());
        if (StringUtils.equals("hour", duration)) {
            periodAttributes.setCurrentStartTimestamp(date.minusHours(1).toDate().getTime());
            periodAttributes.setPreviousEndTimestamp(date.minusHours(1).toDate().getTime());
            periodAttributes.setPreviousStartTimestamp(date.minusHours(2).toDate().getTime());
            periodAttributes.setGranularityType(GranularityType.ONE_MIN);
        } else if (StringUtils.equals("day", duration)) {
            periodAttributes.setCurrentStartTimestamp(date.minusDays(1).toDate().getTime());
            periodAttributes.setPreviousEndTimestamp(date.minusDays(1).toDate().getTime());
            periodAttributes.setPreviousStartTimestamp(date.minusDays(2).toDate().getTime());
            periodAttributes.setGranularityType(GranularityType.ONE_HOUR);
        } else if (StringUtils.equals("week", duration)) {
            periodAttributes.setCurrentStartTimestamp(date.minusWeeks(1).toDate().getTime());
            periodAttributes.setPreviousEndTimestamp(date.minusWeeks(1).toDate().getTime());
            periodAttributes.setPreviousStartTimestamp(date.minusWeeks(2).toDate().getTime());
            periodAttributes.setGranularityType(GranularityType.SIX_HOUR);
        }
        return periodAttributes;
    }


    public List<Long> aggregateAverage(final List<NudgeData> nudgeDataList) {

        return null;

    }

    /**
     * Assigns the nudgeData object to a sample compared to the number of samples desired, the startDate and the endDate
     * of the segment.
     *
     * @param nudgeData
     * @param numberOfSamples
     * @param startDate
     * @param endDate
     * @return
     */
    public Integer identifySample(NudgeData nudgeData, int numberOfSamples, Date startDate, Date endDate) {

        if (nudgeData == null || nudgeData.getCollectingDate() == null) {
            throw new IllegalArgumentException("The nudgeData and the collectingDate must not be null");
        }
        if (numberOfSamples == 0) {
            throw new IllegalArgumentException("The integer 'number of samples' must not be equal to 0");
        }
        if (startDate == null) {
            throw new IllegalArgumentException("The start date must not be null");
        }
        if (endDate == null) {
            throw new IllegalArgumentException("The end date must not be null");
        }
        if (!(startDate.before(endDate))) {
            throw new IllegalArgumentException("The start date must be inferior to the end date");
        }

        Integer sampleNumber = null;

        if (nudgeData.getCollectingDate() != null) {

            double numerator = nudgeData.getCollectingDate().getTime() - startDate.getTime();

            double denominator = (endDate.getTime() - startDate.getTime()) / numberOfSamples;

            if (denominator != 0) {
                Double number = Math.floor(numerator / denominator);
                sampleNumber = number.intValue();
            }
        }
        return sampleNumber;
    }


    /**
     * Aggregate the nudgeData to the corresponding point.
     *
     * @param points
     * @param sampleNumber
     * @param nudgeData
     */
    public void aggregateData(final ConsolidatePoint[] points, final int sampleNumber, final NudgeData nudgeData) {

        // TODO validate arguments: points with sample number
        if (points[sampleNumber] == null) {
            points[sampleNumber] = new ConsolidatePoint();
        }


        // TODO split the following in methods ?

        // aggregate counts
        Long oldCounts = 0l;
        if (points[sampleNumber].getCounts() != null) {
            oldCounts = points[sampleNumber].getCounts();
        }
        points[sampleNumber].setCounts(oldCounts + nudgeData.getCount());

        // aggregate cumul
        Double oldCumul = 0.;
        if (points[sampleNumber].getCumulResponseTime() != null) {
            oldCumul = points[sampleNumber].getCumulResponseTime();
        }
        // this field is a weighted average, it will be divided by the sum of count when no nudgeData will be added.
        points[sampleNumber].setCumulResponseTime(oldCumul + (nudgeData.getAvgResponseTime() * nudgeData.getCount()));

        // aggregate errors
        Integer oldErrors = 0;
        if (points[sampleNumber].getErrors() != null) {
            oldErrors = points[sampleNumber].getErrors();
        }
        points[sampleNumber].setErrors(oldErrors + nudgeData.getErrors());

        // aggregate satisfying clicks
        Long oldSatisfying = 0l;
        if (points[sampleNumber].getSumOfSatisfyingClicks() != null) {
            oldSatisfying = points[sampleNumber].getSumOfSatisfyingClicks();
        }
        points[sampleNumber].setSumOfSatisfyingClicks(oldSatisfying + nudgeData.getSatisfyingClicks());

        // aggregate tolerable clicks
        Long oldTolerable = 0l;
        if (points[sampleNumber].getSumOfTolerableClicks() != null) {
            oldTolerable = points[sampleNumber].getSumOfTolerableClicks();
        }
        points[sampleNumber].setSumOfTolerableClicks(oldTolerable + nudgeData.getTolerableClicks());

        // increment the number of sample for the ConsolidatePoint
        points[0].setNumberOfSamples(points[0].getNumberOfSamples() + 1);
    }

    public void computeConsolidatePoints(final ConsolidatePoint[] points) {

        // TODO validate if the used data are not null


        for (ConsolidatePoint point : points) {

            // compute the apdex
            Double apdex;
            apdex = point.getSumOfSatisfyingClicks() + 0.5;
            apdex = apdex * point.getSumOfTolerableClicks();
            apdex = apdex / point.getCounts();
            point.setApdex(apdex);

            // compute the avg response time. Warning, pre-requisite : the sum of cumulResponseTime must be * by currents counts ?
            Double avgResponseTime = point.getCumulResponseTime() / point.getCounts();
            point.setAvgResponseTime(avgResponseTime);

        }
    }
}
